package com.xiong.m3u8;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import org.apache.commons.lang3.StringUtils;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.javacv.*;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.List;
import java.util.UUID;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.Frame;
import org.bytedeco.opencv.opencv_core.IplImage;


import javax.imageio.ImageIO;

public class FfmpegUtil {

    /**
     * 水印字体
     */
    private static final Font FONT = new Font("Microsoft Yahei", Font.BOLD, 18);
    /**
     * 透明度
     */
    private static final AlphaComposite COMPOSITE = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f);
    /**
     * 水印之间的间隔
     */
    private static final int X_MOVE = 300;
    /**
     * 水印之间的间隔
     */
    private static final int Y_MOVE = 140;


    private static final int FRAME_RATE = 30;
    private static final int VIDEO_BITRATE = 1048576;
    private static final int COMPRESS_WIDTH = 320;

    public static String convertImageToBase64(String imagePath) throws IOException {
        File file = new File(imagePath);
        FileInputStream fileInputStream = new FileInputStream(file);
        byte[] bytes = new byte[(int) file.length()];
        fileInputStream.read(bytes);
        String base64Image = Base64.getEncoder().encodeToString(bytes);
        fileInputStream.close();
        return base64Image;
    }

    public static void videoMerge(List<String> videoList, String output) throws FrameRecorder.Exception, FrameGrabber.Exception, IOException {
        // 帧抓取器 以第一个视频为蓝本开始抓取
        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(videoList.get(0));
        // 载入
        grabber.start();

        FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(output, grabber.getImageWidth(), grabber.getImageHeight());
        // 视频编解码器
        recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
        // 音频编解码器
        recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
        // 与原始视频保持一致的音频参数
        recorder.setSampleRate(grabber.getSampleRate());
        recorder.setAudioChannels(grabber.getAudioChannels());
        recorder.setAudioBitrate(grabber.getAudioBitrate());

        // 输出流封装格式
        recorder.setFormat("mp4");
        // 视频帧率
        recorder.setFrameRate(grabber.getFrameRate());
        // 设置分辨率格式 yuv420p
        recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
        // 视频比特率
        recorder.setVideoBitrate(grabber.getVideoBitrate());

        // 开始编码
        recorder.start();
        // 设置帧
        Frame frame;
        // 循环处理每个视频文件
        for (String videoFile : videoList) {
            FFmpegFrameGrabber grabberTemp = new FFmpegFrameGrabber(videoFile);
            grabberTemp.start();
            while ((frame = grabberTemp.grab()) != null) {
                // 添加并编码帧
                recorder.record(frame);
            }
            // 关闭当前视频帧抓取器
            grabberTemp.stop();
            grabberTemp.release();
        }
        // 关闭帧编解码器
        recorder.stop();
        recorder.release();
        // 关闭帧抓取器
        grabber.stop();
        grabber.release();
    }



    public static File cutVideo(File inFile, File outFile, long beginSecond, long endSecond) throws Exception {
        FFmpegFrameGrabber grabber = null;
        FFmpegFrameRecorder recorder = null;
        try {
            grabber = new FFmpegFrameGrabber(inFile);
            grabber.start();
            if (beginSecond < 0 || (grabber.getLengthInTime() / 1000000L) <= endSecond) {
                return null;
            }
            long numFrame = (long) ((endSecond - beginSecond) * grabber.getFrameRate()) * 2; // 120 * 25

                // grabber.setVideoTimestamp(beginSecond * 1000000L);
            grabber.setVideoFrameNumber((int) (grabber.getFrameRate() * beginSecond)); // 第分钟开始

            recorder = new FFmpegFrameRecorder(outFile, grabber.getImageWidth(), grabber.getImageHeight());
            recorder.setFormat("mp4");
            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
            recorder.setVideoBitrate(grabber.getVideoBitrate());
            recorder.setFrameRate(grabber.getFrameRate());
            recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
            recorder.setAudioChannels(2);
            recorder.start();

            Frame frame;
            long num = 0;
            while ((frame = grabber.grabFrame()) != null && (numFrame >= num)) {
                num++;
                // 处理每一帧的图像或音频数据
                recorder.record(frame);
            }
            return outFile;
        }catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (grabber != null) {
                    grabber.stop();
                    grabber.release();
                }
                if (recorder != null) {
                    recorder.stop();
                    recorder.release();
                }
            } catch (FFmpegFrameGrabber.Exception e1) {
                e1.printStackTrace();
            }
        }
        return null;
    }


    public static void addWaterImage(String srcImgPath,String waterMarkContent,String outImgPath){
        try {
            Color markContentColor = new Color(180,180,180);
            // 读取原图片信息
            File srcFile = new File(srcImgPath);
            File outFile = new File(outImgPath);
            BufferedImage srcImg = ImageIO.read(srcFile);
            // 图片宽、高
            int imgWidth = srcImg.getWidth();
            int imgHeight = srcImg.getHeight();
            // 图片缓存
            BufferedImage bufImg = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
            // 创建绘图工具
            Graphics2D graphics = bufImg.createGraphics();
            // 画入原始图像
            graphics.drawImage(srcImg, 0, 0, imgWidth, imgHeight, null);
            // 设置水印颜色
            createText(markContentColor, waterMarkContent, bufImg, imgWidth, imgHeight, graphics);
            // 输出图片
            try (FileOutputStream fos = new FileOutputStream(outFile);){
                ImageIO.write(bufImg, "jpg", fos);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void addWaterVideo(String inputPath, String waterMarkContent, String outputPath) {
            long l = System.currentTimeMillis();
            File file = new File(inputPath);
            // 抓取视频资源
            Frame frame;
            try(FFmpegFrameGrabber frameGrabber = new FFmpegFrameGrabber(file)){
                frameGrabber.start();
                try (FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputPath, frameGrabber.getImageWidth(), frameGrabber.getImageHeight(), frameGrabber.getAudioChannels())){
                    recorder.setFormat("mp4");
                    recorder.setSampleRate(frameGrabber.getSampleRate());
                    recorder.setFrameRate(frameGrabber.getFrameRate());
                    recorder.setTimestamp(frameGrabber.getTimestamp());
                    recorder.setVideoBitrate(frameGrabber.getVideoBitrate());
                    recorder.setVideoCodec(frameGrabber.getVideoCodec());
                    recorder.start();
                    Color markContentColor = new Color(180,180,180);
                    while (true){
                        frame = frameGrabber.grabFrame();
                        if(frame == null){
                            break;
                        }
                        //判断图片帧
                        if(frame.image != null){
                            IplImage iplImage = Java2DFrameUtils.toIplImage(frame);
                            BufferedImage bufImg = Java2DFrameUtils.toBufferedImage(iplImage);
                            int imgWidth = iplImage.width();
                            int imgHeight = iplImage.height();
                            // 加水印
                            Graphics2D graphics = bufImg.createGraphics();
                            // 创建绘图工具
                            createText(markContentColor, waterMarkContent, bufImg, imgWidth, imgHeight, graphics);

                            Frame newFrame = Java2DFrameUtils.toFrame(bufImg);
                            recorder.record(newFrame);
                        }
                        //设置音频
                        if(frame.samples != null){
                            recorder.recordSamples(frame.sampleRate,frame.audioChannels,frame.samples);
                        }
                    }
                    recorder.stop();
                    recorder.release();
                    frameGrabber.stop();
                }
            }catch (Exception e) {
                e.printStackTrace();
            }

    }

    private static void createText(Color markContentColor, String waterMarkContent, BufferedImage bufImg, int imgWidth, int imgHeight, Graphics2D graphics) {
        // 设置水印颜色
        graphics.setColor(markContentColor);
        // 设置水印透明度
        graphics.setComposite(COMPOSITE);
        // 设置倾斜角度
   //     graphics.rotate(Math.toRadians(-35), (double) bufImg.getWidth() / 2,
     //           (double) bufImg.getHeight() / 2);
        // 设置水印字体
        graphics.setFont(FONT);
        // 消除java.awt.Font字体的锯齿
        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);


        // 字体高度
        int markHeight = FONT.getSize();

        // 设置水印的位置为左上角
        int xCoordinate = 10; // 调整水印的X坐标
        int yCoordinate = markHeight + 10; // 调整水印的Y坐标

        // 添加水印
        graphics.drawString(waterMarkContent, xCoordinate, yCoordinate);
        graphics.dispose();
    }




    //压缩视频
    public static void compressVideo(File file, Boolean checkCompress){
        FFmpegFrameGrabber frameGrabber = new FFmpegFrameGrabber(file.getAbsolutePath());
        String fileName = null;

        Frame captured_frame = null;

        FFmpegFrameRecorder recorder = null;

        try {
            frameGrabber.start();
            fileName = file.getAbsolutePath().replace(".mp4", "_edited.mp4");


            int height = frameGrabber.getImageHeight();
            int widht = frameGrabber.getImageWidth();
            if(checkCompress ){
                height = calculateHeight(frameGrabber.getImageWidth(), frameGrabber.getImageHeight(), COMPRESS_WIDTH);
                widht = COMPRESS_WIDTH;

            }
            recorder = new FFmpegFrameRecorder(fileName, widht, height, frameGrabber.getAudioChannels());
            recorder.setFrameRate(FRAME_RATE);
            //下面这行打开就报错
            //recorder.setSampleFormat(frameGrabber.getSampleFormat());
            recorder.setSampleRate(frameGrabber.getSampleRate());
            //recorder.setAudioChannels(1);
            recorder.setVideoOption("preset", "veryfast");
            // yuv420p,像素
            recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
            recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
            recorder.setFormat("mp4");
            //比特
            //recorder.setVideoBitrate(VIDEO_BITRATE);
            recorder.start();

            while (true) {
                try {
                    captured_frame = frameGrabber.grabFrame();
                    if (captured_frame == null) {
                        System.out.println("!!! end cvQueryFrame");
                        break;
                    }
                    recorder.setTimestamp(frameGrabber.getTimestamp());
                    recorder.record(captured_frame);
                } catch (Exception e) {
                }
            }
            recorder.stop();
            recorder.release();
            frameGrabber.stop();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //file.delete();
        //return fileName;
    }

    private static int calculateHeight(int w, int h, int nw){
        double s = Integer.valueOf(h).doubleValue() / Integer.valueOf(w).doubleValue();
        int height = (int) (nw * s);
        //如果宽和高不是偶数recorder.start();会报错
        if(height % 2 !=0){
            height += 1;
        }
        return height;
    }

    //获取视频时长
    public static Double getVideoTime(File video){
        try {
            FFmpegFrameGrabber fFmpegFrameGrabber = new FFmpegFrameGrabber(video);
            fFmpegFrameGrabber.start();
            // 获取视频的总帧数
            int lengthInFrames = fFmpegFrameGrabber.getLengthInFrames();
            double frameNumber = fFmpegFrameGrabber.getFrameRate();
            // 视频的时长
            Double intLength = lengthInFrames / frameNumber;

            return intLength;
        }catch (Exception e){
            e.printStackTrace();
        }
        return Double.valueOf("0");
    }

    //获取视频封面
    public static File getVideoPic(File video, String picPath) {
        FFmpegFrameGrabber ff = new FFmpegFrameGrabber(video);
        try {
            ff.start();
            int lenght = ff.getLengthInFrames();
            int i = 0;
            Frame f = null;
            while (i < lenght) {
                // 过滤前5帧，避免出现全黑的图片，依自己情况而定
                f = ff.grabFrame();
                if ((i > 5) && (f.image != null)) {
                    break;
                }
                i++;
            }
            // 截取的帧图片
            Java2DFrameConverter converter = new Java2DFrameConverter();
            BufferedImage srcImage = converter.getBufferedImage(f);
            int srcImageWidth = srcImage.getWidth();
            int srcImageHeight = srcImage.getHeight();

            // 对截图进行等比例缩放(缩略图)
            int width = 480;
            int height = (int) (((double) width / srcImageWidth) * srcImageHeight);
            BufferedImage thumbnailImage = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
            thumbnailImage.getGraphics().drawImage(srcImage.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null);

            File picFile = new File(picPath);
            File parentFile=new File(picFile.getParent());
            if(!parentFile.exists()){
                parentFile.mkdirs();
            }

            ImageIO.write(thumbnailImage, "jpg", picFile);

            ff.stop();
            return picFile;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * mp4转m3u8
     *
     * @param filePathName       需要转换文件
     * @param toFilePath 需要转换的文件路径
     */
    public static String mp4ToM3u8(String filePathName, String toFilePath) throws Exception {

        avutil.av_log_set_level(avutil.AV_LOG_INFO);
        FFmpegLogCallback.set();

        boolean isStart = true;// 该变量建议设置为全局控制变量，用于控制录制结束
        //加载文件
        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(filePathName);
        //grabber.setAudioChannels(1);
        grabber.start();
        String fileName = UUID.randomUUID().toString().replaceAll("-", "");
        File tempFile3 = new File(toFilePath, fileName + ".m3u8");

        String prefixName = toFilePath + File.separator + fileName;
        //生成加密key
        String secureFileName = prefixName + ".key";
        byte[] secureRandom = getSecureRandom();
        FileUtil.writeBytes(secureRandom,secureFileName);

        String toHex = Convert.toHex(secureRandom);
        String keyInfoPath = toFilePath + File.separator +"key.keyinfo";
        //写入加密文件
        writeKeyInfo(keyInfoPath,fileName + ".key",secureFileName,toHex);

        FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(tempFile3, grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
        //格式方式
        recorder.setFormat("hls");
        //关于hls_wrap的说明，hls_wrap表示重复覆盖之前ts切片，这是一个过时配置，ffmpeg官方推荐使用hls_list_size 和hls_flags delete_segments代替hls_wrap
        //设置单个ts切片的时间长度（以秒为单位）。默认值为2秒
        recorder.setOption("hls_time", "10");
        //不根据gop间隔进行切片,强制使用hls_time时间进行切割ts分片
        recorder.setOption("hls_flags", "split_by_time");

        //设置播放列表条目的最大数量。如果设置为0，则列表文件将包含所有片段，默认值为5
        // 当切片的时间不受控制时，切片数量太小，就会有卡顿的现象
        recorder.setOption("hls_list_size", "0");
        //自动删除切片，如果切片数量大于hls_list_size的数量，则会开始自动删除之前的ts切片，只保留hls_list_size个数量的切片
        recorder.setOption("hls_flags", "delete_segments");
        //ts切片自动删除阈值，默认值为1，表示早于hls_list_size+1的切片将被删除
        recorder.setOption("hls_delete_threshold", "1");
        /*hls的切片类型：
         * 'mpegts'：以MPEG-2传输流格式输出ts切片文件，可以与所有HLS版本兼容。
         * 'fmp4':以Fragmented MP4(简称：fmp4)格式输出切片文件，类似于MPEG-DASH，fmp4文件可用于HLS version 7和更高版本。
         */
        recorder.setOption("hls_segment_type", "mpegts");
        //指定ts切片生成名称规则，按数字序号生成切片,例如'file%03d.ts'，就会生成file000.ts，file001.ts，file002.ts等切片文件
        //recorder.setOption("hls_segment_filename", toFilePath + "-%03d.ts");
        recorder.setOption("hls_segment_filename", toFilePath + File.separator + fileName + "-%5d.ts");
        //加密
        recorder.setOption("hls_key_info_file", keyInfoPath);
        // 设置第一个切片的编号
//        recorder.setOption("start_number", String.valueOf(tsCont));
//        recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);

        System.out.println(toFilePath+" 启动Hls转码录制器……");
        // 转码

        //      设置零延迟
        //recorder.setVideoOption("tune", "zerolatency");
        recorder.setVideoOption("tune", "fastdecode");
        // 快速
        recorder.setVideoOption("preset", "ultrafast");
//        recorder.setVideoOption("crf", "26");
        recorder.setVideoOption("threads", "12");
        recorder.setVideoOption("vsync", "2");
        recorder.setFrameRate(grabber.getFrameRate());// 设置帧率
//        recorder.setGopSize(25);// 设置gop,与帧率相同，相当于间隔1秒chan's一个关键帧
//		recorder.setVideoBitrate(100 * 1000);// 码率500kb/s
        recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
//        recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
        recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);


        recorder.start(grabber.getFormatContext());
        AVPacket packet;
        while ((packet = grabber.grabPacket()) != null) {
            try {
                recorder.recordPacket(packet);
            } catch (FrameRecorder.Exception e) {

            }
        }
        recorder.setTimestamp(grabber.getTimestamp());
        recorder.stop();
        recorder.release();
        grabber.stop();
        grabber.release();

        File dest = new File(filePathName);
        if (dest.isFile() && dest.exists()) {
           // dest.delete();
            //log.warn("临时文件 {}已删除", dest.getName());
        }
        //log.info("转码m3u8：{}", tempFile3.getAbsolutePath());
        return tempFile3.getAbsolutePath();
    }


    /**
     * 安全安全随机
     *
     * @return {@link byte[]}
     */
    public static byte[] getSecureRandom(){
        byte[] bytes = new byte[16];
        new SecureRandom().nextBytes(bytes);
        return bytes;
    }

    /**
     * 写入关键文件数据
     *
     * @param keyInfoPath 路径
     * @param decrypt    解密
     * @param encrypt    加密
     */
    public static void writeKeyInfo(String keyInfoPath,String decrypt,String encrypt,String IV) throws IOException {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(keyInfoPath));){
            writer.write(decrypt);
            writer.newLine();
            writer.write(encrypt);
            writer.newLine();
            if(StringUtils.isNotBlank(IV)){
                writer.write(IV);
            }
            writer.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }



}
